
#include "voice_common.h"
#include "gkt_dac.h"

#if GKT_CONFIG_VOICE_OUTPUT_ENABLE

static voice_output_s s_voice_output;

#ifdef GKT_CONFIG_VOICE_OUTPUT_OPAMP_EN_PIN

#ifdef GKT_CONFIG_VOICE_OUTPUT_POWER_CTRL_LEVEL
#define VOICE_OUTPUT_OPAMP_ENABLE_LEVEL	\
	GKT_CONFIG_VOICE_OUTPUT_POWER_CTRL_LEVEL
#else
#define VOICE_OUTPUT_OPAMP_ENABLE_LEVEL	0
#endif
#if VOICE_OUTPUT_OPAMP_ENABLE_LEVEL
#define VOICE_OUTPUT_OPAMP_DISABLE	0
#else
#define VOICE_OUTPUT_OPAMP_DISABLE	1
#endif

#define voice_output_opamp_init()	\
	do {	\
		int __opamp_retval = gkt_gpio_config(GKT_CONFIG_VOICE_OUTPUT_OPAMP_EN_PIN,	\
									GKT_GPIO_F_OUTPUT, NULL);	\
		if (__opamp_retval != GKT_SUCCESS)	\
			gkt_error("voice_output: init opamp gpio failed : %d\n", __opamp_retval);	\
	} while (0)
#define voice_output_opamp_enable()	\
	do {	\
		int __opamp_retval = gkt_gpio_set_pin_level(GKT_CONFIG_VOICE_OUTPUT_OPAMP_EN_PIN, 	\
									VOICE_OUTPUT_OPAMP_ENABLE_LEVEL);	\
		if (__opamp_retval != GKT_SUCCESS)	\
			gkt_error("voice_output: enable opamp gpio failed : %d\n", __opamp_retval);	\
	} while (0)

#define voice_output_opamp_disable()	\
	do {	\
		gkt_gpio_config(GKT_CONFIG_VOICE_OUTPUT_OPAMP_EN_PIN,	\
									GKT_GPIO_F_OUTPUT|GKT_GPIO_F_OUTPUT_HIGH, NULL);	\
	} while (0)

#else
#define voice_output_opamp_init()	do {} while (0)
#define voice_output_opamp_enable()	do {} while (0)
#define voice_output_opamp_disable()	do {} while (0)
#endif

void gkt_voice_output_opamp_init(void)
{
	voice_output_opamp_init();
}
void gkt_voice_output_enable_opamp(void)
{
	voice_output_opamp_enable();
}
void gkt_voice_output_disable_opamp(void)
{
	voice_output_opamp_disable();
}


#if VOICE_OUTPUT_ENC_META_BUFFER_STATIC
static uint8_t s_voice_output_enc_buffer[VOICE_OUTPUT_ENC_META_DATA_SIZE];
#endif

void voice_output_status_notify(voice_output_status_e status)
{
	s_voice_output.status = status;
	gkt_semaphore_release(s_voice_output.semaphore);
}

static void voice_output_multiplay_timer_callback(void *params)
{
	int retval;

	gkt_unused(params);

	retval = gkt_mutex_lock(s_voice_output.mutex, 
					VOICE_OUTPUT_SYNC_TIMEOUT);
	if (GKT_SUCCESS == retval) {
		if (s_voice_output.state & VOICE_OUTPUT_STATE_TIMER_START) {
			s_voice_output.state &= ~VOICE_OUTPUT_STATE_TIMER_START;
			s_voice_output.state |= VOICE_OUTPUT_STATE_TIMER_EXPIRE;
			gkt_semaphore_release(s_voice_output.semaphore);
		}

		gkt_mutex_unlock(s_voice_output.mutex);
	}
}

static void voice_output_stop(void)
{
	gkt_trace("voice_output: [STOP] state(0x%x), block_index(%u)\n", 
				s_voice_output.state, s_voice_output.block_index);

	if (s_voice_output.state & VOICE_OUTPUT_STATE_PLAY_START) {
		s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_START;
		voice_output_hw_stop();
	}

	if (s_voice_output.state & VOICE_OUTPUT_STATE_TIMER_START) {
		s_voice_output.state &= ~VOICE_OUTPUT_STATE_TIMER_MASK;
		gkt_soft_timer_stop(s_voice_output.timer_id);
	}
}

static int voice_output_play(void)
{
	int retval;

	gkt_trace("voice_output: [PLAY] (%u) state(0x%x) ...\n", 
				s_voice_output.playback_attr.times,
				s_voice_output.state);

	s_voice_output.block_index = 0;
	s_voice_output.total_left_frame_nums = s_voice_output.total_frame_nums;
	s_voice_output.meta_id_index = 0;
	memset(s_voice_output.meta_buffer, 0, s_voice_output.meta_buffer_size);
	retval = voice_output_get_all_meta_data(&s_voice_output);
	if (retval != GKT_SUCCESS) {
		gkt_error("voice_output: [PLAY] get meta data failed %d\n", retval);
		return retval;
	}
	retval = voice_output_decode_blocks(&s_voice_output);
	if (retval != GKT_SUCCESS) {
		gkt_error("voice_output: [PLAY] decode failed %d\n", retval);
		return retval;
	}

	s_voice_output.status = VOICE_OUTPUT_ST_INVALID;
	s_voice_output.state |= VOICE_OUTPUT_STATE_PLAY_START;
	retval = voice_output_hw_start(s_voice_output.volume, s_voice_output.dec_buffer + VOICE_OUTPUT_DEC_HEAD_CUTOFF_BYTES,
				s_voice_output.total_frame_nums * VOICE_OUTPUT_DEC_FRAME_BYTES * GKT_VOICE_DEC_CONVERSION_SAMPLE_MULTIPLE - VOICE_OUTPUT_DEC_TOTAL_CUTOFF_BYTES);
	if (retval != GKT_SUCCESS) {
		s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_START;
		return retval;
	}

//	voice_output_opamp_enable();

	s_voice_output.playback_attr.times--;

	return GKT_SUCCESS;
}

static void voice_output_notify_user(int state)
{
	if (s_voice_output.callback)
		(*s_voice_output.callback)(s_voice_output.private_data, state);
}

static void voice_output_process(void)
{
	voice_output_status_e status;
	int retval = GKT_SUCCESS;

	gkt_trace("voice_output_process: state = 0x%x\n", 
		s_voice_output.state);

	if (!(s_voice_output.state & VOICE_OUTPUT_STATE_PLAY_REQ)) {
		voice_output_stop();
		return;
	}

	if (s_voice_output.state & VOICE_OUTPUT_STATE_PLAY_START) {
		gkt_irq_global_disable();
		status = s_voice_output.status;
		s_voice_output.status = VOICE_OUTPUT_ST_INVALID;
		gkt_irq_global_enable();

		gkt_trace("voice_output_process: status = %u\n", status);
		if (status < VOICE_OUTPUT_ST_INVALID) {
			if (VOICE_OUTPUT_ST_END == status) {
				voice_output_stop();

				if (!s_voice_output.playback_attr.times) {
					s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_REQ;
					voice_output_notify_user(GKT_VOICE_OUTPUT_ST_FINISH);
				}
				else {
					if (s_voice_output.playback_attr.interval) {
						retval = gkt_soft_timer_start(s_voice_output.timer_id, 
										s_voice_output.playback_attr.interval);
						if (GKT_SUCCESS == retval)
							s_voice_output.state |= VOICE_OUTPUT_STATE_TIMER_START;
						else {
							gkt_error("voice_output: start timer failed : %d\n", retval);
							s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_REQ;
							voice_output_notify_user(GKT_VOICE_OUTPUT_ST_ERROR);
						}
					}
					else {
						retval = voice_output_play();
						if (retval != GKT_SUCCESS) {
							s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_REQ;
							voice_output_notify_user(GKT_VOICE_OUTPUT_ST_ERROR);
						}
					}
				}			
			}
			else {
//			voice_output_opamp_disable();
				voice_output_stop();

				s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_REQ;
				voice_output_notify_user(GKT_VOICE_OUTPUT_ST_ERROR);
			}
		}
	}
#if 1
	if (s_voice_output.state & VOICE_OUTPUT_STATE_TIMER_EXPIRE) {
		s_voice_output.state &= ~VOICE_OUTPUT_STATE_TIMER_EXPIRE;
		retval = voice_output_play();
		if (retval != GKT_SUCCESS) {
			s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_REQ;
			voice_output_notify_user(GKT_VOICE_OUTPUT_ST_ERROR);
		}
	}
#endif
}

static void voice_output_routine(void *params)
{
	int retval;

	do {
		retval = gkt_semaphore_acquire(s_voice_output.semaphore, 
						GKT_INFINITE);
		gkt_trace("voice_output_routine enter\n");
		if (GKT_SUCCESS == retval) {
			gkt_mutex_lock(s_voice_output.mutex, GKT_INFINITE);
			gkt_wdte_mark(GKT_WDTE_ID(VOICE_OUTPUT), GKT_WDTE_ST_BUSY);
			voice_output_process();
			gkt_wdte_mark(GKT_WDTE_ID(VOICE_OUTPUT), GKT_WDTE_ST_IDLE);
			gkt_mutex_unlock(s_voice_output.mutex);
		}
	} while (1);
}

uint32_t voice_output_get_total_frame_nums(voice_output_s *output,
				const gkt_voice_combined_attr_s *combined_attr)
{
	gkt_voice_meta_id_t meta_id;
	uint32_t meta_id_index;
	uint32_t total_frame_nums, meta_frame_nums;

	total_frame_nums = 0;
	meta_id_index = 0;
	while (meta_id_index < combined_attr->meta_nums) {
		meta_id = combined_attr->meta_ids[meta_id_index];
		if (meta_id < output->c_header->meta_nums) {
#if !GKT_VOICE_DEC_INPUT_FRAME_SIZE
			meta_frame_nums = output->c_meta_attrs[meta_id].frame_nums;
#else
			meta_frame_nums = output->c_meta_attrs[meta_id].data_size / GKT_VOICE_DEC_INPUT_FRAME_SIZE;
#endif
			if (!meta_frame_nums)
				gkt_error("voice_output: empty meta_id(%u).\n", meta_id);

			total_frame_nums += meta_frame_nums;
		}
		else
			gkt_error("voice_output: invalid meta_id(%u)\n", meta_id);

		meta_id_index++;
	};
	if (total_frame_nums > 0)
		total_frame_nums += VOICE_OUTPUT_APPEND_FRAMES;

	gkt_trace("voice_output: meta_nums(%u), total_frames(%u)\n", 
				combined_attr->meta_nums, total_frame_nums);
	
	return total_frame_nums;
}

static uint32_t voice_output_get_duration_ms(uint32_t total_frame_nums,
				const gkt_voice_output_playback_attr_s *playback_attr)
{
	uint32_t duration_ms = 0;

	duration_ms = total_frame_nums * GKT_VOICE_DEC_FRAME_DURATION - VOICE_OUTPUT_TOTAL_CUTOFF_TIME;
	if (playback_attr && (playback_attr->times > 1)) {
		duration_ms += playback_attr->interval;
		duration_ms *= playback_attr->times;
		duration_ms -= playback_attr->interval;
	}

	return duration_ms;
}

#define VOICE_OUTPUT_PLAYBACK_INTERVAL_UNIT	100	/* unit in ms */
int gkt_voice_output_play(
				const gkt_voice_combined_attr_s *combined_attr,
				const gkt_voice_output_playback_attr_s *playback_attr,
				uint32_t private_data, uint32_t *duration_ms)
{
	int retval;

	if (combined_attr 
		&& combined_attr->meta_ids 
		&& (combined_attr->meta_nums > 0)) 
	{
		voice_output_sync_resource(&s_voice_output);

		if (!s_voice_output.c_meta_attrs)
			return GKT_ENORESOURCE;

		retval = gkt_mutex_lock(s_voice_output.mutex, 
						VOICE_OUTPUT_SYNC_TIMEOUT);
		if (GKT_SUCCESS == retval) {
			if (s_voice_output.state & VOICE_OUTPUT_STATE_PLAY_REQ) {
				voice_output_stop();
				s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_REQ;
			}

			s_voice_output.combined_attr = combined_attr;
			s_voice_output.total_frame_nums = 
				voice_output_get_total_frame_nums(&s_voice_output, combined_attr);
			if (!s_voice_output.total_frame_nums) {
				gkt_mutex_unlock(s_voice_output.mutex);
				return GKT_EPARAM;
			}

			if (playback_attr) {
				s_voice_output.playback_attr = *playback_attr;

				if (!s_voice_output.playback_attr.times)
					s_voice_output.playback_attr.times = 1;
				else if (s_voice_output.playback_attr.times > GKT_VOICE_OUTPUT_PLAYBACK_TIMES_MAX)
					s_voice_output.playback_attr.times = GKT_VOICE_OUTPUT_PLAYBACK_TIMES_MAX;

				if (s_voice_output.playback_attr.interval > GKT_VOICE_OUTPUT_PLAYBACK_INTERVAL_MAX)
					s_voice_output.playback_attr.interval = GKT_VOICE_OUTPUT_PLAYBACK_INTERVAL_MAX;
				else if (s_voice_output.playback_attr.interval > 0) {
					s_voice_output.playback_attr.interval += VOICE_OUTPUT_PLAYBACK_INTERVAL_UNIT - 1;
					s_voice_output.playback_attr.interval /= VOICE_OUTPUT_PLAYBACK_INTERVAL_UNIT;
					s_voice_output.playback_attr.interval *= VOICE_OUTPUT_PLAYBACK_INTERVAL_UNIT;
				}
			}
			else {
				s_voice_output.playback_attr.times = 1;
				s_voice_output.playback_attr.interval = 0;
			}

			s_voice_output.private_data = private_data;

			if (duration_ms) {
				*duration_ms = voice_output_get_duration_ms(s_voice_output.total_frame_nums,
									&s_voice_output.playback_attr);
				gkt_trace("voice_output_play: duration_ms = %u\n", *duration_ms);
			}

			s_voice_output.state |= VOICE_OUTPUT_STATE_PLAY_REQ;
			retval = voice_output_play();
			if (retval != GKT_SUCCESS)
				s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_REQ;

			gkt_mutex_unlock(s_voice_output.mutex);
		}
	}
	else
		retval = GKT_EPARAM;	

	return retval;
}

void gkt_voice_output_stop(void)
{
	gkt_mutex_lock(s_voice_output.mutex, GKT_INFINITE);
	if (s_voice_output.state & VOICE_OUTPUT_STATE_PLAY_REQ) {
		voice_output_stop();
		s_voice_output.state &= ~VOICE_OUTPUT_STATE_PLAY_REQ;
	}
	gkt_mutex_unlock(s_voice_output.mutex);
}

void gkt_voice_output_register_callback(
				gkt_voice_output_callback_fn callback)
{
	s_voice_output.callback = callback;
}

uint32_t gkt_voice_get_duration_ms(
			const gkt_voice_combined_attr_s *combined_attr,
			const gkt_voice_output_playback_attr_s *playback_attr)
{
	uint32_t total_frame_nums;
	uint32_t duration_ms = 0;

	voice_output_sync_resource(&s_voice_output);

	if (combined_attr 
		&& combined_attr->meta_ids 
		&& (combined_attr->meta_nums > 0))
	{
		total_frame_nums = voice_output_get_total_frame_nums(
								&s_voice_output, combined_attr);
		if (total_frame_nums > 0)
			duration_ms = voice_output_get_duration_ms(total_frame_nums, playback_attr);
	}

	return duration_ms;
}

uint32_t gkt_voice_output_get_meta_nums(void)
{
	voice_output_sync_resource(&s_voice_output);
	return s_voice_output.c_header ? s_voice_output.c_header->meta_nums : 0;
}

#if !GKT_VOICE_OUTPUT_VOLUME_DISABLE
gkt_voice_output_volume_e gkt_voice_output_get_volume(void)
{
	return s_voice_output.volume;
}

void gkt_voice_output_set_volume(gkt_voice_output_volume_e volume)
{
	gkt_info("voice_output: volume %u --> %u\n",
		s_voice_output.volume, volume);
	if (volume < GKT_VOICE_OUTPUT_VOLUME_MAX)
		s_voice_output.volume = volume;
}
#endif

int voice_output_init(void)
{
	gkt_thread_t thread;
	int retval;

	/* default volume */
#if !GKT_VOICE_OUTPUT_VOLUME_DISABLE
	s_voice_output.volume = GKT_VOICE_OUTPUT_VOLUME_DEFAULT;
#endif

	s_voice_output.mutex = gkt_mutex_create(0);
	if (!s_voice_output.mutex) {
		gkt_error("voice_output_init: create mutex failed!\n");
		return GKT_ENORESOURCE;
	}

	s_voice_output.semaphore = gkt_semaphore_create(1, 0);
	if (!s_voice_output.semaphore) {
		gkt_error("voice_output_init: create semaphore failed!\n");
		return GKT_ENORESOURCE;
	}

	s_voice_output.timer_id = gkt_soft_timer_create(
								GKT_SOFT_TIMER_ONCE, 
								voice_output_multiplay_timer_callback, 
								NULL);
	if (!s_voice_output.timer_id) {
		gkt_error("voice_output_init: create timer failed!\n");
		return GKT_ENORESOURCE;
	}

	retval = voice_output_check_resource(&s_voice_output);
	if (retval != GKT_SUCCESS)
		return retval;

	retval = voice_output_hw_init(&s_voice_output);
	if (retval != GKT_SUCCESS)
		return retval;

#if VOICE_OUTPUT_DEC_BLOCK_BUFFER_STATIC
	s_voice_output.dec_buffer = &s_voice_output_dec_buffer[0];
#else
	s_voice_output.dec_buffer = (uint8_t *)gkt_malloc((VOICE_OUTPUT_DEC_BUFFER_SIZE)
				+ VOICE_OUTPUT_DEC_BUFFER_APPEND_BYTES);
	if (!s_voice_output.dec_buffer) {
		gkt_error("voice_output_init: malloc block buffer failed!\n");
		return GKT_ENOMEMORY;
	}
#endif

	s_voice_output.meta_buffer = (uint8_t *)gkt_malloc(VOICE_OUTPUT_ENC_ALL_META_DATA_SIZE);
	if (!s_voice_output.meta_buffer) {
		gkt_error("voice_output_init: malloc meta buffer failed!\n");
		return GKT_ENOMEMORY;
	}
	s_voice_output.meta_buffer_size = VOICE_OUTPUT_ENC_ALL_META_DATA_SIZE;
	
	thread = gkt_thread_create(voice_output_routine, NULL,
					GKT_THREAD_PRIORITY_BY_NAME(VOICE_OUTPUT_THREAD_PRIORITY),
					VOICE_OUTPUT_THREAD_STACKSIZE,
					NULL);
	if (!thread) {
		gkt_error("voice_output_init: create thread failed!\n");
		return GKT_ENORESOURCE;
	}

	return GKT_SUCCESS;
}

#endif

